package br.ufrgs.meetapp.database;

import java.util.ArrayList;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.location.Location;
import android.util.Log;
import br.ufrgs.meetapp.models.DBMessage;


public class PositionMessageDAO {

	private static MFTDatabase mftDatabaseHelper;
	private SQLiteDatabase mftSQLiteDatabase;
	
	private int AMOUNT_OF_MESSAGES_TO_TRIEVE = 10;
	
	private long firstRetrievedId = -1;
	
	public PositionMessageDAO(Context context) {
		
		// Just one Helper for all threads, it must be this way
		if (mftDatabaseHelper == null){
			mftDatabaseHelper = new MFTDatabase(context);
		}
	}
	
	/**
	 * The connection with the database must be explicity open and MUST be closed afet used
	 * @throws SQLException if the database cannot be opened for writing
	 */
	private void open() {
		
		try {
			
			mftSQLiteDatabase = mftDatabaseHelper.getWritableDatabase();
			log("Database opened");
			
		} catch (Exception exception) {
			
			log("Error trying to open the database");
			
			System.out.println("Error in PositionMessageDAO.open - trying to open the database");
			if (exception.getMessage() != null)
				System.out.println(exception.getMessage());
			exception.printStackTrace();
		}
		
	}

	/**
	 * Close the connection with the database
	 */
	private void close() {
		try {
			mftDatabaseHelper.close();
			log("Database closed");
			
		} catch (Exception exception) {
			log("Error trying to close the database");
			System.out.println("Error in PositionMessageDAO.close - trying to ");
			if (exception.getMessage() != null)
				System.out.println(exception.getMessage());
			exception.printStackTrace();
		}
	}
	
	/**
	 * This is used to retrieve top AMOUNT_OF_MESSAGES_TO_TRIEVE of messages 
	 * @return
	 */
	public ArrayList<DBMessage> getTopMessageList() {
		ArrayList<DBMessage> positionMessageList = new ArrayList<DBMessage>();
		try {
			open();
			Cursor cursor = mftSQLiteDatabase.query(
					MFTDatabase.EVENT_MESSAGE_TABLE_NAME, null, null,
					null, null, null, MFTDatabase.COLUMN_ID + " LIMIT "
							+ AMOUNT_OF_MESSAGES_TO_TRIEVE);
			if (cursor != null && cursor.getCount() > 0) {
				cursor.moveToFirst();
				positionMessageList = cursorToDBMessageArrayList(cursor);
				cursor.close();
			}
			close();
		} catch (Exception exception) {
			System.out.println("Error in PositionMessageDAO.getMessageList() - trying to open database or retrieve messages");			
			if(exception.getMessage() != null)
				System.out.println(exception.getMessage());
			exception.printStackTrace();
		}
		return positionMessageList;
	}

	/**
	 * Deletes the specified amount of messages from the database starting from the ID stored when the user called getMessageList <br><br>
	 * <b>obs:</b> this amount should be exactly the same amount that was send to the server. 
	 * Remember to send messages synchronously since the messages will be deleted in order
	 *  
	 * @param numberOfMessages - number of messages that were send with no error and should be deleted
	 * @return the number of affected rows
	 */
	public int deletePositionMessageFromFirstToNumberOfMessages(int numberOfMessages) {
		
		int rowsAffected;
		open();
		if(numberOfMessages > 0 && firstRetrievedId != -1)
			rowsAffected = mftSQLiteDatabase.delete(MFTDatabase.EVENT_MESSAGE_TABLE_NAME, MFTDatabase.COLUMN_ID + " < " + firstRetrievedId + numberOfMessages, null);
		else
			rowsAffected = 0;
		close();
		return rowsAffected;
	}
	
	/**
	 * Deletes the messages wich id in in the array
	 *  
	 * @param numberOfMessages - number of messages that were send with no error and should be deleted
	 * @return the number of affected rows
	 */
	public int deleteListOfPosition(ArrayList<Long> messages) {
		
		int rowsAffected = 0;
		try {
			open();
			if(messages.size() > 0 ){
				String inValues = messages.toString().replace("[", "").replace("]", "");
				rowsAffected = mftSQLiteDatabase.delete(MFTDatabase.EVENT_MESSAGE_TABLE_NAME, MFTDatabase.COLUMN_ID + " IN ( " + inValues + ") ", null);
			}
			else
				rowsAffected = 0;
			close();
		} catch (Exception e) {
			log("Exception in deleteListOfPosition method at PositionMessageDAO : "+e.getMessage());
			e.printStackTrace();
		}
		return rowsAffected;
	}
	
	/***
	 * Inserts a new positionMessage(must be "protocolized" already) and a new last valid position into the database 
	 * @param positionMessage - position message value to be inserted into the database
	 * @param location - location that generated the message
	 * @return
	 */
	public boolean insertPosition(byte[] positionMessage, Location location) {
		
		boolean success = false;
		
		try {
			
			// Get old last valid location 
			Location oldLastValidLocation = getLastValidLocation();
			
			open();
			
			mftSQLiteDatabase.beginTransaction();
			
			if (updateLastValidLocation(location, oldLastValidLocation) && insertMessage(positionMessage)){
				mftSQLiteDatabase.setTransactionSuccessful();
				success = true;
				log("Message inserted");
			}
		
		} catch (Exception exception) {

			log("Error in message insertion");
			System.out.println("Error in PositionMessageDAO.insertPositionMessage - trying to insert new information in the database");
			if (exception.getMessage() != null)
				System.out.println(exception.getMessage());
			exception.printStackTrace();
		} finally {
			mftSQLiteDatabase.endTransaction();
			close();
		}
		
		return success;
	}
	
	public boolean executeMessageInsertion(byte[] message) {
		boolean success = false;
		
		try {
			open();
			
			success = insertMessage(message);
			
		} catch (Exception exception) {

			System.out.println("Error in PositionMessageDAO.executeMessageInsertion - trying to ");
			if (exception.getMessage() != null) {
				System.out.println(exception.getMessage());
			}
			exception.printStackTrace();
		} finally {
			close();
		}
		
		return success;
	}
	
	
	
	/***
	 * Database MUST be already open to call this method and MUST be closed after the return. This method inserts a position message into the database
	 * @param message
	 * @return true if no errors occurred trying to insert the position message into database
	 * @throws Exception
	 */
	private boolean insertMessage(byte[] message) throws Exception {
		
		boolean success = false;
		
		ContentValues messageValues = new ContentValues();
		messageValues.put(MFTDatabase.COLUMN_VALUE, message);
		
		if (mftSQLiteDatabase.insert(MFTDatabase.EVENT_MESSAGE_TABLE_NAME, null, messageValues) != -1) {
			success = true;
		}
		
		return success;
	}
	
	/***
	 * Database MUST be already open to call this method and MUST be closed after the return. This method updates the last valid location into the database. 
	 * @param location - new last valid location to be added
	 * @param oldLastValidLocation - old last valid location to be deleted
	 * @return true if no errors occurred trying to update last valid location
	 * @throws Exception
	 */
	private boolean updateLastValidLocation(Location location, Location oldLastValidLocation) throws Exception {

		boolean success = true;
		
		ContentValues newLastValidPositionValues = new ContentValues();
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_ACCURACY, location.getAccuracy());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_ALTITUDE, location.getAltitude());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_BEARING, location.getBearing());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_HAS_ACCURACY, location.hasAccuracy());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_HAS_ALTITUDE, location.hasAltitude());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_HAS_BEARING, location.hasBearing());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_HAS_SPEED, location.hasSpeed());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_LATITUDE, location.getLatitude());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_LONGITUDE, location.getLongitude());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_PROVIDER, location.getProvider());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_SPEED, location.getSpeed());
		newLastValidPositionValues.put(MFTDatabase.LAST_VALID_LOCATION_TIME, location.getTime());
		
		if (oldLastValidLocation == null) {
			if (mftSQLiteDatabase.insert(MFTDatabase.LAST_VALID_LOCATION_TABLE_NAME, null, newLastValidPositionValues) == -1){
				success = false;
			}
		} else if (oldLastValidLocation.getTime() < location.getTime()) {
			if (mftSQLiteDatabase.update(MFTDatabase.LAST_VALID_LOCATION_TABLE_NAME, newLastValidPositionValues, null, null) < 1){
				success = false;
			}
		} 
		
		return success;
	}
	
	/***
	 * Get last valid location stores in the database
	 * @return - last valid location
	 */
	public Location getLastValidLocation() {
		Location location = null;
		open();
		try {
			Cursor cursor = mftSQLiteDatabase.query(MFTDatabase.LAST_VALID_LOCATION_TABLE_NAME, null, null, null,
					null, null, null);
			
			if (cursor != null && cursor.getCount() > 0) {
				cursor.moveToFirst();
				
				location = new Location(cursor.getString(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_PROVIDER)));
				if (cursor.getInt(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_HAS_ACCURACY)) == 1){
					location.setAccuracy(cursor.getFloat(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_ACCURACY)));
				}
				if (cursor.getInt(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_HAS_ALTITUDE)) == 1){
					location.setAltitude(cursor.getDouble(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_ALTITUDE)));
				}
				if (cursor.getInt(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_HAS_BEARING)) == 1){
					location.setBearing(cursor.getFloat(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_BEARING)));
				}
				if (cursor.getInt(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_HAS_SPEED)) == 1){
					location.setSpeed(cursor.getFloat(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_SPEED)));
				}
				
				location.setLatitude(cursor.getDouble(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_LATITUDE)));
				location.setLongitude(cursor.getDouble(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_LONGITUDE)));
				location.setTime(cursor.getLong(cursor.getColumnIndexOrThrow(MFTDatabase.LAST_VALID_LOCATION_TIME)));
				
				cursor.close();
			}
			
			return location;
		} catch (Exception exception) {

			log("Error in message insertion");
			System.out.println("Error in PositionMessageDAO.insertPositionMessage - trying to insert new information in the database");
			if (exception.getMessage() != null)
				System.out.println(exception.getMessage());
			exception.printStackTrace();
			return location;
		} finally {
			close();
		}
	}
	
	/**
	 * Retrieves a list of messages from the cursor
	 * @param cursor
	 * @return positionMessage ArrayList<DBMessage>
	 */
	private ArrayList<DBMessage> cursorToDBMessageArrayList(Cursor cursor) {
		
		ArrayList<DBMessage> positionMessageList = new ArrayList<DBMessage>();
		
		while (!cursor.isAfterLast()) {
			DBMessage message = new DBMessage();
			message.setId(cursor.getLong(0));
			message.setPositionMessage(cursor.getBlob(1));
			positionMessageList.add(message);
			cursor.moveToNext();
		}
		
		return positionMessageList;
	}
	
	private void log(String message) {
		Log.d("PositionMessageDAO", message);
	}
}
